package com.sharp.framework.service.prometheus;

import com.sharp.framework.service.redis.RedisService;
import com.sharp.framework.utils.NetUtil;
import com.sharp.kernel.utils.ReflectionUtil;
import com.sharp.kernel.utils.StringUtil;
import io.prometheus.client.CollectorRegistry;
import io.prometheus.client.Gauge;
import io.prometheus.client.exporter.PushGateway;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Title: PushGatewayService
 * Description: Copyright: Copyright (c) 2019 Company: BHFAE
 *
 * @author Sharp
 * @date 2021/3/4 16:08
 */
@Slf4j
@Service
public class PushGatewayService {

    private static Map<String, Gauge> gaugeMap = new ConcurrentHashMap<>();
    private static Set<Class<?>> metrics = new HashSet<>();

    @Value("${my.push.gateway.url}")
    private String url;
    @Value("${server.port}")
    private String localPort;
    @Value("${my.project.name}")
    private String projectName;
    @Resource
    private RedisService redisService;

    @Async
    public void pushGateWay(Object req) {
        pushGateWay(req, 1D, false);
    }

    /**
     * @param req      推送指标
     * @param value    推送值
     * @param absolute 绝对值/服务启动以来累加值
     *                 <p>
     *                 历史值统计@see storeMetric
     */
    @Async
    public void pushGateWay(Object req, double value, boolean absolute) {
        if (StringUtils.isBlank(url)) {
            return;
        }
        Class<?> clazz = req.getClass();
        List<Field> fieldList = ReflectionUtil.getFields(clazz);
        List<String> labelNames = new ArrayList<>();
        List<String> labelValues = new ArrayList<>();
        for (Field field : fieldList) {
            labelNames.add(field.getName());
            buildLabelValues(req, labelValues, field);
        }
        Gauge gauge = gaugeMap.get(clazz.getName());
        if (gauge == null) {
            gauge = Gauge
                    .build(clazz.getSimpleName(), projectName + " project push gateway metric.")
                    .labelNames(labelNames.toArray(new String[0]))
                    .create();
            gaugeMap.put(clazz.getName(), gauge);
        }
        if (absolute) {
            gauge.labels(labelValues.toArray(new String[0])).set(value);
        } else {
            gauge.labels(labelValues.toArray(new String[0])).inc(value);
        }

        String jobName = clazz.getSimpleName() + "_job";
        pushGateWay(gauge, jobName);
    }

    private void buildLabelValues(Object req, List<String> labelValues, Field field) {
        DateTimeFormat dateTimeFormatAnnotation = field.getAnnotation(DateTimeFormat.class);
        if (dateTimeFormatAnnotation != null && StringUtils.isNotEmpty((dateTimeFormatAnnotation).pattern())) {
            labelValues.add(StringUtil.parse(ReflectionUtil.getValue(req, field.getName(), Object.class), dateTimeFormatAnnotation.pattern()));
        } else {
            labelValues.add(StringUtil.parse(ReflectionUtil.getValue(req, field.getName(), Object.class)));
        }
    }

    private void pushGateWay(Gauge gauge, String jobName) {
        if (StringUtils.isBlank(url)) {
            return;
        }
        PushGateway pushGateway = new PushGateway(url);
        Map<String, String> groupingKey = new HashMap<>(1);
        groupingKey.put("instance", NetUtil.getLocalIp() + ":" + localPort);
        CollectorRegistry registry = new CollectorRegistry();
        gauge.register(registry);
        try {
            pushGateway.push(gauge, jobName, groupingKey);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Async
    public void pushGateWay(Object req, boolean absolute) {
        pushGateWay(req, 1D, absolute);
    }

    public void storeMetric(Object req) {
        Class<?> clazz = req.getClass();
        String key = buildKey(clazz);
        redisService.zSetIncrScore(key, req, 1D);
        metrics.add(clazz);
    }

    private String buildKey(Class<?> clazz) {
        return "pushGateWay" + ":" + projectName + ":" + clazz.getSimpleName() + ":" + NetUtil.getLocalIp() + ":" + localPort;
    }

    @Scheduled(cron = "0/30 * * * * ?")
    public void schedulePush() {

        for (Class<?> clazz : metrics) {
            String key = buildKey(clazz);
            List<String> labelNames = new ArrayList<>();
            List<Field> fieldList = ReflectionUtil.getFields(clazz);
            for (Field field : fieldList) {
                labelNames.add(field.getName());
            }
            Gauge gauge = Gauge
                    .build(clazz.getSimpleName(), projectName + " project push gateway metric.")
                    .labelNames(labelNames.toArray(new String[0]))
                    .create();
            Set<Object> objectSet = redisService.zSetRange(key, 0, -1, Object.class);
            for (Object entity : objectSet) {
                double score = getScore(entity);
                List<String> labelValues = new ArrayList<>();
                for (Field field : fieldList) {
                    buildLabelValues(entity, labelValues, field);
                }
                gauge.labels(labelValues.toArray(new String[0])).set(score);
            }
            String jobName = clazz.getSimpleName() + "_job";
            pushGateWay(gauge, jobName);
        }
    }

    private double getScore(Object obj) {
        String key = buildKey(obj.getClass());
        Double score = redisService.zSetScore(key, obj);
        if (score != null) {
            return score;
        } else {
            return 1D;
        }
    }

}
